<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTTP报文发送工具</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        body {
            background-color: #f5f7fa;
            padding: 20px;
            line-height: 1.6;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
        }

        header {
            text-align: center;
            margin-bottom: 30px;
            color: #2c3e50;
        }

        h1 {
            font-size: 28px;
            margin-bottom: 10px;
        }

        .intro {
            color: #7f8c8d;
            font-size: 16px;
        }

        .main-content {
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
        }

        .request-section, .response-section {
            flex: 1;
            min-width: 300px;
            background: white;
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }

        .section-title {
            font-size: 18px;
            margin-bottom: 15px;
            color: #3498db;
            padding-bottom: 8px;
            border-bottom: 2px solid #3498db;
        }

        .form-group {
            margin-bottom: 15px;
        }

        label {
            display: block;
            margin-bottom: 5px;
            color: #2c3e50;
            font-weight: 500;
        }

        input, select, textarea, button {
            width: 100%;
            padding: 10px;
            border: 1px solid #bdc3c7;
            border-radius: 4px;
            font-size: 14px;
        }

        input:focus, select:focus, textarea:focus {
            outline: none;
            border-color: #3498db;
            box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
        }

        .method-url {
            display: flex;
            gap: 10px;
        }

        .method-url select {
            width: 120px;
        }

        .method-url input {
            flex: 1;
        }

        .headers-container {
            margin-bottom: 10px;
        }

        .header-row {
            display: flex;
            gap: 10px;
            margin-bottom: 10px;
        }

        .header-row input {
            flex: 1;
        }

        .header-row button {
            width: 40px;
            background-color: #e74c3c;
            color: white;
            border: none;
            cursor: pointer;
        }

        .add-btn {
            background-color: #2ecc71;
            color: white;
            border: none;
            cursor: pointer;
            margin-bottom: 20px;
        }

        .payload-tabs {
            display: flex;
            gap: 5px;
            margin-bottom: 10px;
            border-bottom: 1px solid #bdc3c7;
            padding-bottom: 5px;
        }

        .payload-tab {
            padding: 5px 15px;
            background-color: #ecf0f1;
            border-radius: 4px 4px 0 0;
            cursor: pointer;
        }

        .payload-tab.active {
            background-color: #3498db;
            color: white;
        }

        .payload-content {
            display: none;
        }

        .payload-content.active {
            display: block;
        }

        .form-data-row {
            display: flex;
            gap: 10px;
            margin-bottom: 10px;
            align-items: center;
        }

        .form-data-row input[type="text"] {
            flex: 1;
        }

        .form-data-row input[type="file"] {
            flex: 2;
            padding: 5px;
        }

        .form-data-row button {
            width: 40px;
            background-color: #e74c3c;
            color: white;
            border: none;
            cursor: pointer;
        }

        textarea {
            min-height: 150px;
            resize: vertical;
        }

        .action-buttons {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }

        .send-btn {
            background-color: #3498db;
            color: white;
            border: none;
            cursor: pointer;
            flex: 2;
            font-weight: 500;
        }

        .clear-btn {
            background-color: #95a5a6;
            color: white;
            border: none;
            cursor: pointer;
            flex: 1;
        }

        .response-info {
            margin-bottom: 15px;
            padding: 10px;
            background-color: #ecf0f1;
            border-radius: 4px;
        }

        .status-code {
            font-weight: bold;
            font-size: 16px;
        }

        .response-tabs {
            display: flex;
            gap: 5px;
            margin-bottom: 10px;
            border-bottom: 1px solid #bdc3c7;
            padding-bottom: 5px;
        }

        .response-tab {
            padding: 5px 15px;
            background-color: #ecf0f1;
            border-radius: 4px 4px 0 0;
            cursor: pointer;
        }

        .response-tab.active {
            background-color: #3498db;
            color: white;
        }

        .response-content {
            display: none;
            max-height: 400px;
            overflow-y: auto;
            background-color: #f8f9fa;
            padding: 15px;
            border-radius: 4px;
            font-family: monospace;
            white-space: pre-wrap;
            word-wrap: break-word;
        }

        .response-content.active {
            display: block;
        }

        .download-btn {
            display: inline-block;
            margin-top: 10px;
            padding: 8px 15px;
            background-color: #2ecc71;
            color: white;
            text-decoration: none;
            border-radius: 4px;
            font-size: 14px;
        }

        .loading {
            display: none;
            text-align: center;
            padding: 20px;
            color: #3498db;
        }

        .error {
            color: #e74c3c;
            padding: 10px;
            background-color: #fadbd8;
            border-radius: 4px;
            margin-bottom: 15px;
            display: none;
        }

        .encoding-info {
            font-size: 12px;
            color: #7f8c8d;
            margin-top: 5px;
            font-style: italic;
        }

        .header-hint {
            font-size: 12px;
            color: #7f8c8d;
            margin-top: -10px;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
<div class="container">
    <header>
        <h1>HTTP报文发送工具</h1>
        <p class="intro">支持多种请求类型、自定义请求头和文件传输</p>
    </header>

    <div class="main-content">
        <div class="request-section">
            <h2 class="section-title">请求配置</h2>

            <div class="form-group method-url">
                <select id="method">
                    <option value="GET">GET</option>
                    <option value="POST" selected>POST</option>
                    <option value="PUT">PUT</option>
                    <option value="DELETE">DELETE</option>
                    <option value="PATCH">PATCH</option>
                    <option value="HEAD">HEAD</option>
                    <option value="OPTIONS">OPTIONS</option>
                </select>
                <input type="text" id="url" placeholder="输入请求URL" value="https://httpbin.org/post">
            </div>

            <div class="form-group">
                <label>请求头</label>
                <p class="header-hint">所有添加的请求头都会被完整发送，不会被工具自动修改或移除</p>
                <div id="headers-container" class="headers-container">
                    <div class="header-row">
                        <input type="text" placeholder="Key" value="Content-Type">
                        <input type="text" placeholder="Value" value="application/json">
                        <button class="remove-header" disabled>×</button>
                    </div>
                </div>
                <button id="add-header" class="add-btn">+ 添加请求头</button>
            </div>

            <div class="form-group">
                <label>请求体</label>
                <div class="payload-tabs">
                    <div class="payload-tab active" data-type="none">无</div>
                    <div class="payload-tab" data-type="json">JSON</div>
                    <div class="payload-tab" data-type="form-data">Form Data</div>
                    <div class="payload-tab" data-type="text">文本</div>
                </div>

                <div id="payload-none" class="payload-content active">
                    <p>此请求类型不需要请求体</p>
                </div>

                <div id="payload-json" class="payload-content">
                    <textarea id="json-payload" placeholder='{"key": "value"}'>{"name": "test", "value": "example"}</textarea>
                </div>

                <div id="payload-form-data" class="payload-content">
                    <div id="form-data-container">
                        <div class="form-data-row">
                            <input type="text" placeholder="键" value="file">
                            <input type="file" placeholder="值/文件">
                            <button class="remove-form-data" disabled>×</button>
                        </div>
                    </div>
                    <button id="add-form-data" class="add-btn">+ 添加表单字段</button>
                    <p class="encoding-info">提示：中文文件名会自动进行UTF-8编码处理</p>
                </div>

                <div id="payload-text" class="payload-content">
                    <textarea id="text-payload" placeholder="文本内容">这是一段测试文本</textarea>
                </div>
            </div>

            <div class="action-buttons">
                <button id="send-request" class="send-btn">发送请求</button>
                <button id="clear-request" class="clear-btn">清空</button>
            </div>
        </div>

        <div class="response-section">
            <h2 class="section-title">响应结果</h2>

            <div class="loading" id="loading">
                <p>正在发送请求...</p>
            </div>

            <div class="error" id="error"></div>

            <div class="response-info" id="response-info" style="display: none;">
                <div class="status-code" id="status-code"></div>
                <div id="response-time"></div>
                <div id="request-headers-sent" style="margin-top: 5px; font-size: 12px; color: #7f8c8d;"></div>
            </div>

            <div class="response-tabs">
                <div class="response-tab active" data-type="body">响应体</div>
                <div class="response-tab" data-type="headers">响应头</div>
            </div>

            <div id="response-body" class="response-content active"></div>
            <div id="response-headers" class="response-content"></div>

            <div id="download-section" style="display: none;">
                <a id="download-link" class="download-btn">下载文件</a>
            </div>
        </div>
    </div>
</div>

<script>
    // 请求头相关功能
    document.getElementById('add-header').addEventListener('click', function() {
        const container = document.getElementById('headers-container');
        const row = document.createElement('div');
        row.className = 'header-row';
        row.innerHTML = `
            <input type="text" placeholder="Key">
            <input type="text" placeholder="Value">
            <button class="remove-header">×</button>
        `;
        container.appendChild(row);

        // 添加删除功能
        row.querySelector('.remove-header').addEventListener('click', function() {
            container.removeChild(row);
            updateRemoveButtons();
        });

        updateRemoveButtons();
    });

    // 更新删除按钮状态
    function updateRemoveButtons() {
        const rows = document.querySelectorAll('.header-row');
        rows.forEach((row, index) => {
            const btn = row.querySelector('.remove-header');
            btn.disabled = rows.length <= 1;
        });
    }

    // Form Data相关功能
    document.getElementById('add-form-data').addEventListener('click', function() {
        const container = document.getElementById('form-data-container');
        const row = document.createElement('div');
        row.className = 'form-data-row';
        row.innerHTML = `
            <input type="text" placeholder="键">
            <input type="file" placeholder="值/文件">
            <button class="remove-form-data">×</button>
        `;
        container.appendChild(row);

        // 添加删除功能
        row.querySelector('.remove-form-data').addEventListener('click', function() {
            container.removeChild(row);
            updateFormDataRemoveButtons();
        });

        updateFormDataRemoveButtons();
    });

    // 更新Form Data删除按钮状态
    function updateFormDataRemoveButtons() {
        const rows = document.querySelectorAll('.form-data-row');
        rows.forEach((row, index) => {
            const btn = row.querySelector('.remove-form-data');
            btn.disabled = rows.length <= 1;
        });
    }

    // 请求体标签切换
    document.querySelectorAll('.payload-tab').forEach(tab => {
        tab.addEventListener('click', function() {
            // 移除所有active类
            document.querySelectorAll('.payload-tab').forEach(t => t.classList.remove('active'));
            document.querySelectorAll('.payload-content').forEach(c => c.classList.remove('active'));

            // 添加active类到当前标签和内容
            this.classList.add('active');
            const type = this.getAttribute('data-type');
            document.getElementById(`payload-${type}`).classList.add('active');
        });
    });

    // 响应标签切换
    document.querySelectorAll('.response-tab').forEach(tab => {
        tab.addEventListener('click', function() {
            // 移除所有active类
            document.querySelectorAll('.response-tab').forEach(t => t.classList.remove('active'));
            document.querySelectorAll('.response-content').forEach(c => c.classList.remove('active'));

            // 添加active类到当前标签和内容
            this.classList.add('active');
            const type = this.getAttribute('data-type');
            document.getElementById(`response-${type}`).classList.add('active');
        });
    });

    // 处理中文文件名编码的函数
    function encodeChineseFileName(file) {
        // 对于中文文件名，创建一个新的File对象，使用UTF-8编码的文件名
        if (file.name && /[\u4e00-\u9fa5]/.test(file.name)) {
            // 创建一个新的Blob对象，内容与原文件相同
            return new File([file], file.name, {
                type: file.type,
                lastModified: file.lastModified
            });
        }
        return file;
    }

    // 发送请求功能
    document.getElementById('send-request').addEventListener('click', async function() {
        const method = document.getElementById('method').value;
        const url = document.getElementById('url').value;

        // 验证URL
        if (!url) {
            showError('请输入请求URL');
            return;
        }

        // 显示加载状态
        showLoading(true);
        showError('');
        document.getElementById('response-info').style.display = 'none';
        document.getElementById('download-section').style.display = 'none';

        try {
            // 收集请求头 - 确保完全保留用户输入的所有请求头
            const headers = {};
            document.querySelectorAll('.header-row').forEach(row => {
                const key = row.querySelectorAll('input')[0].value.trim();
                const value = row.querySelectorAll('input')[1].value.trim();
                if (key) {
                    headers[key] = value;
                }
            });

            // 收集请求体
            let body = null;
            const activePayloadTab = document.querySelector('.payload-tab.active');
            const payloadType = activePayloadTab.getAttribute('data-type');

            if (payloadType === 'json') {
                const jsonText = document.getElementById('json-payload').value;
                if (jsonText) {
                    try {
                        body = JSON.stringify(JSON.parse(jsonText));
                    } catch (e) {
                        showError('JSON格式不正确');
                        showLoading(false);
                        return;
                    }
                }
            } else if (payloadType === 'form-data') {
                const formData = new FormData();
                document.querySelectorAll('.form-data-row').forEach(row => {
                    const key = row.querySelectorAll('input')[0].value.trim();
                    const fileInput = row.querySelectorAll('input')[1];

                    if (key && fileInput.files.length > 0) {
                        // 处理中文文件名
                        const originalFile = fileInput.files[0];
                        const encodedFile = encodeChineseFileName(originalFile);
                        formData.append(key, encodedFile, encodedFile.name);
                    } else if (key) {
                        formData.append(key, '');
                    }
                });
                body = formData;
                // 注意：这里不再自动删除Content-Type，让浏览器根据FormData自动处理
                // 但如果用户明确设置了Content-Type，会被保留
            } else if (payloadType === 'text') {
                body = document.getElementById('text-payload').value;
            }

            // 记录发送的请求头，用于显示
            const sentHeaders = { ...headers };
            // 如果是FormData且没有设置Content-Type，浏览器会自动设置multipart/form-data
            if (payloadType === 'form-data' && !sentHeaders['Content-Type']) {
                sentHeaders['Content-Type'] = 'multipart/form-data; boundary=auto-generated';
            }

            // 记录请求开始时间
            const startTime = performance.now();

            // 发送请求 - 完全使用用户配置的请求头
            const response = await fetch(url, {
                method: method,
                headers: headers,  // 直接使用用户提供的所有请求头
                body: body,
                mode: 'cors' // 允许跨域请求
            });

            // 计算响应时间
            const endTime = performance.now();
            const responseTime = (endTime - startTime).toFixed(2);

            // 更新响应信息
            document.getElementById('status-code').textContent = `状态码: ${response.status} ${response.statusText}`;
            document.getElementById('response-time').textContent = `响应时间: ${responseTime}ms`;

            // 显示发送的请求头
            let sentHeadersText = "发送的请求头: ";
            Object.entries(sentHeaders).forEach(([key, value]) => {
                sentHeadersText += `${key}=${value}; `;
            });
            document.getElementById('request-headers-sent').textContent = sentHeadersText;

            document.getElementById('response-info').style.display = 'block';

            // 处理响应头
            let headersText = '';
            response.headers.forEach((value, key) => {
                headersText += `${key}: ${value}\n`;
            });
            document.getElementById('response-headers').textContent = headersText;

            // 处理响应体
            const contentType = response.headers.get('content-type') || '';

            // 检查是否是文件类型
            if (contentType.includes('application/octet-stream') ||
                contentType.includes('application/pdf') ||
                contentType.includes('image/') ||
                contentType.includes('video/') ||
                contentType.includes('audio/')) {

                // 处理文件下载
                const blob = await response.blob();
                const url = URL.createObjectURL(blob);
                const downloadLink = document.getElementById('download-link');
                downloadLink.href = url;

                // 尝试从响应头获取文件名
                let fileName = 'download';
                const contentDisposition = response.headers.get('content-disposition');
                if (contentDisposition && contentDisposition.includes('filename=')) {
                    // 解码可能经过编码的文件名
                    fileName = decodeURIComponent(contentDisposition.split('filename=')[1].replace(/"/g, ''));
                }
                downloadLink.download = fileName;
                document.getElementById('download-section').style.display = 'block';

                document.getElementById('response-body').textContent = `响应是二进制文件，点击下方下载按钮保存。\nContent-Type: ${contentType}`;
            } else if (contentType.includes('application/json')) {
                // 处理JSON响应
                const jsonData = await response.json();
                document.getElementById('response-body').textContent = JSON.stringify(jsonData, null, 2);
            } else {
                // 处理文本响应
                const text = await response.text();
                document.getElementById('response-body').textContent = text;
            }

        } catch (error) {
            showError(`请求失败: ${error.message}`);
            console.error('请求错误:', error);
        } finally {
            // 隐藏加载状态
            showLoading(false);
        }
    });

    // 清空请求功能
    document.getElementById('clear-request').addEventListener('click', function() {
        document.getElementById('url').value = '';
        document.getElementById('method').value = 'GET';
        document.getElementById('json-payload').value = '';
        document.getElementById('text-payload').value = '';

        // 重置请求头，保留一个空行
        const headersContainer = document.getElementById('headers-container');
        while (headersContainer.children.length > 1) {
            headersContainer.removeChild(headersContainer.lastChild);
        }
        const firstHeaderRow = headersContainer.querySelector('.header-row');
        firstHeaderRow.querySelectorAll('input')[0].value = '';
        firstHeaderRow.querySelectorAll('input')[1].value = '';
        updateRemoveButtons();

        // 重置Form Data，保留一个空行
        const formDataContainer = document.getElementById('form-data-container');
        while (formDataContainer.children.length > 1) {
            formDataContainer.removeChild(formDataContainer.lastChild);
        }
        const firstFormDataRow = formDataContainer.querySelector('.form-data-row');
        firstFormDataRow.querySelectorAll('input')[0].value = '';
        firstFormDataRow.querySelectorAll('input')[1].value = '';
        updateFormDataRemoveButtons();

        // 切换到无请求体标签
        document.querySelectorAll('.payload-tab').forEach(t => t.classList.remove('active'));
        document.querySelectorAll('.payload-content').forEach(c => c.classList.remove('active'));
        document.querySelector('.payload-tab[data-type="none"]').classList.add('active');
        document.getElementById('payload-none').classList.add('active');
    });

    // 辅助函数：显示/隐藏加载状态
    function showLoading(show) {
        document.getElementById('loading').style.display = show ? 'block' : 'none';
    }

    // 辅助函数：显示错误信息
    function showError(message) {
        const errorElement = document.getElementById('error');
        errorElement.textContent = message;
        errorElement.style.display = message ? 'block' : 'none';
    }

    // 初始化删除按钮事件
    document.querySelectorAll('.remove-header').forEach(btn => {
        btn.addEventListener('click', function() {
            const row = this.closest('.header-row');
            document.getElementById('headers-container').removeChild(row);
            updateRemoveButtons();
        });
    });

    document.querySelectorAll('.remove-form-data').forEach(btn => {
        btn.addEventListener('click', function() {
            const row = this.closest('.form-data-row');
            document.getElementById('form-data-container').removeChild(row);
            updateFormDataRemoveButtons();
        });
    });
</script>
</body>
</html>
